#ifndef OpenCLTemplate_H_
#define OpenCLTemplate_H_
#define PRECISION int

#include <Timer.h>
#include<time.h>
#include <string>
#include <CL/cl.h>

class COpenCLTemplate;
class OpenCLPlatforms
{
	COpenCLTemplate* platform_obj;
	cl_uint num_of_platforms;
	cl_platform_id* platform_ID;
	char** platform_ID_name;	
	PRECISION* matrix_A;
	PRECISION* matrix_B, *matrix_B_transpose;
	PRECISION *MATRIX_C;
	int rows_matrix_A;
	int colums_matrix_A;
	int rows_matrix_B;
	int colums_matrix_B;

public:
	OpenCLPlatforms();
	void SetMatrixA(int* mat)	{ matrix_A = mat; }
	void SetMatrixB(int* mat)	{ matrix_B = mat; }
	void SetMatrixC(int* mat)	{ MATRIX_C = mat; }
	int* GetMatrixA()	{ return matrix_A;}
	int* GetMatrixB()	{ return matrix_B;}
	int* GetMatrixC()	{ return MATRIX_C;}
	//int GetNumberOfThreads(){ return number_of_threads; }
	int GetRowsMatrixA(){ return rows_matrix_A; }
	int GetRowsMatrixB(){ return rows_matrix_B; }
	int GetColumsMatrixA(){ return colums_matrix_A; }
	int GetColumsMatrixB(){ return colums_matrix_B; }

	void Initialize(void *);
	void* MemoryCopy (void *, const void *, int);
	void DetectPlatforms();
	void MatrixGeneration();
	void SetPlatformIDs();
	void CreateThreads();
	int AllocateMemoryCPU();
	void CombineMatrixC();
	void SetPlatformMatrices();
	int MatrixStore();
	
	void SetRowsMatrixA(PRECISION row){ rows_matrix_A = row; }
	void SetRowsMatrixB(PRECISION row){ rows_matrix_B = row; }
	void SetColumsMatrixA(PRECISION col){ colums_matrix_A = col; }
	void SetColumsMatrixB(PRECISION col){ colums_matrix_B = col; }	

//	int GetNumOfPlatforms(){return num_of_platforms;};
	friend int SafeCall(cl_int, const char []=NULL);
	friend void ThreadFunctionForPlatforms(void*);

};

class COpenCLTemplate
{
private:
	// Device data arrays.
	//cl_mem cl_matrix_A;d_input;
	//cl_mem d_output;
	cl_mem cl_matrix_A;
	cl_mem cl_matrix_B_transposer;
	cl_mem cl_matrix_C;

	// ============ OPENCL related parameters ===========
	// OPENCL context/device/program
	cl_context context;
	cl_device_id *devices;
	cl_command_queue commandQueue;
	cl_program program;
	cl_kernel kernel;
	// ==================================================
	
	// Timer variables.
	__int64 tStart;
	__int64 tEnd;
	__int64 tDelta;
	bool tPaused;

public:
	COpenCLTemplate();	// Default width of problem is 256 and multiplier is 2.
	char *SelectedPlatform;
	cl_platform_id platform;
	cl_device_type type;

	PRECISION* matrix_A;
	PRECISION* matrix_B, *matrix_B_transpose;
	int rows_matrix_A;
	int colums_matrix_A;
	int rows_matrix_B;
	int colums_matrix_B;
	int start_thread;
	PRECISION* matrix_C;
	
	int MatrixTranspose();
	int MatrixStore();
	/** Setter functions **/
	void SetMatrixA(PRECISION* mat)	{ matrix_A = mat; }
	void SetMatrixB(PRECISION* mat)	{ matrix_B = mat; }
	void SetMatrixC(PRECISION* mat)	{ matrix_C = mat; }

	void SetRowsMatrixA(PRECISION row){ rows_matrix_A = row; }
	void SetRowsMatrixB(PRECISION row){ rows_matrix_B = row; }
	void SetColumsMatrixA(PRECISION col){ colums_matrix_A = col; }
	void SetColumsMatrixB(PRECISION col){ colums_matrix_B = col; }	
	
	PRECISION* GetMatrixA()	{ return matrix_A;}
	PRECISION* GetMatrixB()	{ return matrix_B;}
	PRECISION* GetMatrixC()	{ return matrix_C;}
	PRECISION GetRowsMatrixA(){ return rows_matrix_A; }
	PRECISION GetRowsMatrixB(){ return rows_matrix_B; }
	PRECISION GetColumsMatrixA(){ return colums_matrix_A; }
	PRECISION GetColumsMatrixB(){ return colums_matrix_B; }
	
	// Memory allocation and initialisation.
	int AllocateMemoryCPU();
	int InitialiseCPU();
	int InitialiseCL();			// Search and allocate a device.
	int AllocateMemoryGPU();
	int InitialiseCLKernelsGPU(); // Build/attach kernels to respective kernel functions and set arguments.
	int RunCLKernels();

	// Complete run encapsulating all the sub-functions.
	int CompleteRun();

	// Timing.
	void StartTimer();
	void StopTimer();
	void ResetTimer();
	double GetElapsedTime();

	std::string convertToString(const char * filename);
	int DisplayArray(const unsigned int, PRECISION*);

	int CleanupCPU();
	int CleanupCL();
	int CleanupGPU();
	~COpenCLTemplate ();
};
#endif // #ifndef OpenCLTemplate_H_
